package com.ttg.common.gxp;

import com.jcraft.jsch.*;
import com.ttg.common.exception.BaseException;
import com.ttg.common.gxp.param.*;
import com.ttg.common.utils.BasicUtils;
import com.ttg.common.utils.DateTimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.lang.reflect.Field;
import java.net.Socket;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 行内系统统一请求工具类
 */
@Slf4j
public class GxpUtil {

    private static final DateTimeFormatter hHmmssFormat = DateTimeFormatter.ofPattern("HHmmss");

    private static final DateTimeFormatter YMD = DateTimeFormatter.ofPattern("yyyyMMdd");

    private static GxpProperties getProperties() {
        return ContextAware.getApplicationContext().getBean(GxpProperties.class);
    }

    private static GxpMessageV2Properties getMessageV2Properties() {
        return ContextAware.getApplicationContext().getBean(GxpMessageV2Properties.class);
    }

    /**
     * 请求gxp并获取响应结果
     */
    private static String socketRequest(String post) {
        try {
            log.error("GXP请求参数:{}", post);
            GxpProperties properties = getProperties();
            Socket socket = new Socket(properties.getAddress(), properties.getPort());
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
            bufferedOutputStream.write(post.getBytes("gbk"));
            bufferedOutputStream.flush();
            BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream());
            bufferedInputStream.mark(8);
            byte[] lengBytes = new byte[8];
            int i = bufferedInputStream.read(lengBytes);
            int count = 0;
            for (; i < 8; ) {
                bufferedInputStream.reset();
                i = bufferedInputStream.read(lengBytes);
                count++;
                if (count > 50) {
                    throw new IOException("响应读取异常");
                }
            }
            int bodyLen = Integer.parseInt(new String(lengBytes));
            String bodyLenStr = String.valueOf(bodyLen);


            byte[] bodyBytes = new byte[bodyLen];
            bufferedInputStream.mark(bodyLen);
            i = bufferedInputStream.read(bodyBytes);
            for (; i < bodyLen; ) {
                bufferedInputStream.reset();
                i = bufferedInputStream.read(bodyBytes);
            }
            socket.close();
            //解码TCP响应的完整报文
            String respData = new String(bodyBytes, "GBK");
            String str = StringUtils.leftPad(bodyLenStr, 8, "0");
            str += respData;
            return str;
        } catch (IOException e) {
            log.error("GXP行内接口请求异常:{}", e.getMessage());
            throw new BaseException(e.getMessage());
        }
    }

    /**
     * 返回字符串的字符长度(中文两个长度)
     */
    public static int stringLength(String value) {
        int valueLength = 0;
        String chinese = "[\u4e00-\u9fa5]";
        for (int i = 0; i < value.length(); i++) {
            String temp = value.substring(i, i + 1);
            char tempChar = temp.charAt(0);
            if (temp.matches(chinese) || isChinesePunctuation(tempChar)) {
                valueLength += 2;
            } else {
                valueLength += 1;
            }
        }
        return valueLength;
    }

    /**
     * 根据UnicodeBlock方法判断中文标点符号
     *
     * @param c 待验证字符
     * @return true-存在，false-不存在
     */
    public static boolean isChinesePunctuation(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        return (ub == Character.UnicodeBlock.GENERAL_PUNCTUATION)
                || (ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION)
                || (ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS)
                || (ub == Character.UnicodeBlock.CJK_COMPATIBILITY_FORMS)
                || (ub == Character.UnicodeBlock.LATIN_1_SUPPLEMENT)
                || (ub == Character.UnicodeBlock.VERTICAL_FORMS);
    }

    /**
     * head头数据解析返回
     *
     * @param xmlHead xmlHead
     * @return SrBankPublicVO
     */
    public static SrBankPublicVO parseHead(String xmlHead) {
        SrBankPublicVO srBankPublicVO = null;
        try {
            JAXBContext ctx = JAXBContext.newInstance(SrBankPublicVO.class);
            Unmarshaller marchaller = ctx.createUnmarshaller();
            XMLInputFactory xif = XMLInputFactory.newFactory();
            xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,false);
            xif.setProperty(XMLInputFactory.SUPPORT_DTD,false);
            XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(xmlHead));
            srBankPublicVO = (SrBankPublicVO) marchaller.unmarshal(xsr);
            return srBankPublicVO;
        } catch (JAXBException | XMLStreamException e) {
            log.error("head头数据解析错误");
            throw new BaseException("head头数据解析错误:" + e.getMessage());
        }
    }

    /**
     * 生成系统跟踪号
     *
     * @param transSerial 交易序列号
     * @return 系统跟踪号
     */
    public static String systemTrackNo(String transSerial) {
        GxpProperties properties = getProperties();
        String transDate = DateTimeUtil.formatYYYYMMDD(LocalDate.now());
        String systemTrackNo = properties.getSysNo() + transDate + transSerial;
        if (systemTrackNo.length() < 8) {
            systemTrackNo = StringUtils.leftPad(systemTrackNo, 8, "0");
        }
        return systemTrackNo;
    }

    /**
     * 生成定长公共头
     *
     * @param transCode 接口码
     * @param sysId     接收系统id
     * @param xmlLength xml报文长度
     * @return 公共头字符串
     */
    public static String charHead(String transCode, String sysId, Integer xmlLength) {
        GxpProperties properties = getProperties();
        String dc = properties.getSYSID();
        dc = StringUtils.rightPad(dc, 8, " ");
        dc += sysId;
        dc = StringUtils.rightPad(dc, 18, " ");
        dc += transCode;
        dc = StringUtils.rightPad(dc, 48, " ");
        int length = stringLength(dc) + xmlLength;
        String strLength = StringUtils.leftPad(Integer.toString(length), 8, "0");
        return strLength + dc;
    }

    /**
     * 对象转xml
     *
     * @param obj o
     * @return str
     */
    public static String objToXml(Object obj) {
        // 创建输出流
        StringWriter sw = new StringWriter();
        try {
            // 利用jdk中自带的转换类实现
            JAXBContext context = JAXBContext.newInstance(obj.getClass());
            Marshaller marshaller = context.createMarshaller();
            // 格式化xml输出的格式
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            // 将对象转换成输出流形式的xml
            marshaller.marshal(obj, sw);
        } catch (JAXBException e) {
            log.error("对象转xml失败！", e);
            throw new BaseException("对象转xml失败:" + e.getMessage());
        }
        String s = sw.toString();
        return s.substring(55);
    }

    /**
     * 生成公共请求头
     *
     * @param head head
     * @return 请求头
     */
    public static String head(SrBankHead head) {
        GxpProperties properties = getProperties();
        String transDate = DateTimeUtil.formatYYYYMMDD(LocalDate.now());
        String transTime = LocalDateTime.now().format(hHmmssFormat);
        head.setTransDate(transDate);
        head.setTransTime(transTime);
        String systemTrackNo = systemTrackNo(head.getTransSerial());
        head.setSystemTrackNo(systemTrackNo);
        head.setSendNode(properties.getSYSID());
        return objToXml(head);
    }

    /**
     * 生成交易流水
     */
    public static String creatTransSerial() {
        return LocalDateTime.now().format(hHmmssFormat) + RandomStringUtils.randomNumeric(2);
    }


    /**
     * 发送短信V2
     */
    public static SrBankPublicVO<SrBankMessagesVO> srBankMessagesV2(SrBankMessages messages) {
        GxpProperties properties = getProperties();
        GxpMessageV2Properties messageV2Properties = getMessageV2Properties();
        SrBankMessagesV2 messagesV2 = new SrBankMessagesV2();
        messagesV2.setMsgId("" + messages.getSmsId());
        messagesV2.setClntId(messageV2Properties.getMessagesCodeV2ClntId());
        messagesV2.setPSwd(messageV2Properties.getMessagesCodeV2Pswd());
        messagesV2.setRuleId(messageV2Properties.getMessagesCodeV2RuleId());
        messagesV2.setMsgFmt(messageV2Properties.getMessagesCodeV2MsgFmt());
        try {
            messagesV2.setMsgCntnt(BasicUtils.byte2hex(messages.getMessgaeText().getBytes("GBK")));
            messagesV2.setRcvMblnmbr(BasicUtils.byte2hex(messages.getMobileNo().getBytes("GBK")));
        } catch (Exception e) {
            log.error("发送短信V2接口字段转换报错", e);
            throw new BaseException("短信V2发送失败:" + e.getMessage());
        }
        String transCode = properties.getMessagesCodeV2();
        //生成交易流水
        String transSerial = creatTransSerial();
        //生成公共请求头
        SrBankHead headPojo = new SrBankHead();
        headPojo.setTransCode(transCode);
        headPojo.setRecvNode(properties.getXXTZPTID());
        headPojo.setTransSerial(transSerial);
        String head = head(headPojo);
        String message = "<root>" + head;
        //生成xml的body
        String body = objToXml(messagesV2);
        message += body + "</root>";
        message = message.replaceAll("\\s*", "");
        //拼接定长报文
        String s = charHead(transCode, properties.getXXTZPTID(), stringLength(message));
        s += message;
        // 请求接口后将xml转为对象
        String msg = socketRequest(s);
        log.error("GXP发送短信V2返回数据:{}", msg);
        if (StringUtils.isBlank(msg)) {
            return null;
        }
        // 处理数据进行xml解析
        msg = msg.substring(62);
        String msgHead = "";
        String msgBody = "";
        if (msg.contains("</head><body>")) {
            String[] split = msg.split("</head><body>");
            msgHead = split[0] + "</head>";
            msgBody = "<body>" + split[1];
            msgBody = msgBody.substring(0, msgBody.length() - 7);
        } else {
            msgHead = msg.substring(0, msg.length() - 7);
        }
        // 获取head中的数据转换为对象 (通用该方法)
        SrBankPublicVO<SrBankMessagesVO> srBankPublicVO = parseHead(msgHead);
        if (Objects.isNull(srBankPublicVO)) {
            return null;
        }
        // 获取body中的数据 (不同接口使用不同vo实体)
        if (StringUtils.isNotBlank(msgBody)) {
            SrBankMessagesVO msgBodyVO = null;
            try {
                JAXBContext ctx = JAXBContext.newInstance(SrBankMessagesVO.class);
                Unmarshaller marchaller = ctx.createUnmarshaller();
                XMLInputFactory xif = XMLInputFactory.newFactory();
                xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,false);
                xif.setProperty(XMLInputFactory.SUPPORT_DTD,false);
                XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(msgBody));
                msgBodyVO = (SrBankMessagesVO) marchaller.unmarshal(xsr);
            } catch (JAXBException | XMLStreamException e) {
                log.error("发送短信V2接口报文转对象报错:{}", e.getMessage());
                throw new BaseException("发送短信V2接口报文转对象报错:" + e.getMessage());
            }
            srBankPublicVO.setBody(msgBodyVO);
        }
        return srBankPublicVO;
    }


}
